package uz.rt.dicom.tag;

import uz.rt.dicom.DicomException;

import java.nio.ByteOrder;

public class DicomTag implements Comparable<DicomTag>
{
// ------------------------------ FIELDS ------------------------------

    // 16 bit unsigned binary group
    private int group;
    // 16 bit unsigned binary element
    private int element;

// --------------------------- CONSTRUCTORS ---------------------------

    /**
     * Dicom tag constructor
     *
     * @param group   16 bit unsigned binary group
     * @param element 16 bit unsigned binary element
     */
    public DicomTag(int group, int element)
    {
        this.group = group;
        this.element = element;
    }

    /**
     * Dicom tag constructor, assumes byte order Little Endian
     *
     * @param group   16 bit unsigned binary group
     * @param element 16 bit unsigned binary element
     * @throws Exception If an IO error occurs.
     */
    public DicomTag(byte[] group, byte[] element) throws DicomException
    {
        setGroup(group, ByteOrder.LITTLE_ENDIAN);
        setGroup(element, ByteOrder.LITTLE_ENDIAN);
    }

    /**
     * Set the 16 bit group
     *
     * @param bytes     16 bit unsigned binary group
     * @param byteOrder Little or Big Endian byte order
     * @throws Exception If an IO error occurs.
     */
    public void setGroup(byte[] bytes, ByteOrder byteOrder) throws DicomException
    {
        if (bytes.length != 2)
            throw new DicomException("Group length must be equal to 2 (cfr 2 bytes)");
        int a = (bytes[0] & 0xff);
        int b = (bytes[1] & 0xff);
        if (byteOrder == ByteOrder.BIG_ENDIAN) group = a << 8 | b;
        if (byteOrder == ByteOrder.LITTLE_ENDIAN) group = b << 8 | a;
    }

    /**
     * Dicom tag constructor, assumes byte order Little Endian
     *
     * @param group     16 bit unsigned binary group
     * @param element   16 bit unsigned binary element
     * @param byteOrder Little or Big Endian byte order
     * @throws Exception If an IO error occurs.
     */
    public DicomTag(byte[] group, byte[] element, ByteOrder byteOrder) throws DicomException
    {
        setGroup(group, byteOrder);
        setGroup(element, byteOrder);
    }

// --------------------- GETTER / SETTER METHODS ---------------------

    /**
     * Get the 16 bit element
     *
     * @return 16 bit unsigned binary element--
     */
    public int getElement()
    {
        return element;
    }

    /**
     * Set the 16 bit element
     *
     * @param element 16 bit unsigned binary element
     */
    public void setElement(int element)
    {
        this.element = element;
    }

    /**
     * Get the 16 bit group
     *
     * @return 16 bit unsigned binary group
     */
    public int getGroup()
    {
        return group;
    }

    /**
     * Set the 16 bit group
     *
     * @param group 16 bit unsigned binary group
     */
    public void setGroup(int group)
    {
        this.group = group;
    }

// ------------------------ CANONICAL METHODS ------------------------

    @Override
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (!(o instanceof DicomTag)) return false;

        DicomTag dicomTag = (DicomTag) o;

        return element == dicomTag.element && group == dicomTag.group;
    }

    @Override
    public int hashCode()
    {
        int result = group;
        result = 31 * result + element;
        return result;
    }

    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        sb.append(groupToString());
        sb.append(",");
        sb.append(elementToString());
        sb.append(")");
        return sb.toString();
    }

    /**
     * Returns a string in which the group is written as a hex value
     *
     * @return Group string in hex format
     */
    public String groupToString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("0x");
        String gs = Integer.toHexString(this.group);
        for (int i = gs.length(); i < 4; i++)
            sb.append("0");
        sb.append(gs);
        return sb.toString();
    }

    /**
     * Returns a string in which the element is written as a hex value
     *
     * @return Element string in hex format
     */
    public String elementToString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append("0x");
        String el = Integer.toHexString(this.element);
        for (int i = el.length(); i < 4; i++)
            sb.append("0");
        sb.append(el);
        return sb.toString();
    }

// ------------------------ INTERFACE METHODS ------------------------


// --------------------- Interface Comparable ---------------------

    public int compareTo(DicomTag o)
    {
        if (group != o.group) return Integer.compare(group, o.group);
        else if (element != o.element) return Integer.compare(element, o.element);
        else return 0;
    }

// -------------------------- OTHER METHODS --------------------------

    /**
     * Get a long representation of the dicom tag. The group element is stored in the highest 16 bit, the element is
     * stored in the lowest 16 bits.
     *
     * @return Long representation of the dicom tag.
     */
    public long getLong()
    {
        return ((long) group << 16) + ((long) element & 0xffff);
    }

    /**
     * Set the 16 bit element
     *
     * @param bytes     16 bit unsigned binary element
     * @param byteOrder Little or Big Endian byte order
     * @throws Exception If an IO error occurs.
     */
    public void setElement(byte[] bytes, ByteOrder byteOrder) throws DicomException
    {
        if (bytes.length != 2)
            throw new DicomException("Group length must be equal to 2 (cfr 2 bytes)");
        int a = (bytes[0] & 0xff);
        int b = (bytes[1] & 0xff);
        if (byteOrder == ByteOrder.BIG_ENDIAN) element = a << 8 | b;
        if (byteOrder == ByteOrder.LITTLE_ENDIAN) element = b << 8 | a;
    }
}
